package des.handling;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;

import javax.swing.JProgressBar;

import des.algorithm.DES;
import des.algorithm.Encryption;
import des.algorithm.Encryption.EncryptionType;
import des.algorithm.TripleDES;

public class FileHandler extends Handler {
	/* Status die bijhoudt als de encoder aan het encrypten of decrypten is */
	protected static EncryptionType encryptionType;

		
	public static long counter;
	public static int progress;
	static private JProgressBar progBar;

	public static int encryptFile(Path pathIn, Path pathOut, long[] keys, EncryptionType type, 
			JProgressBar pbBar) throws IOException {
		progBar = pbBar;
		encryptionType = type;
		status = Status.ENCRYPTING;
		return handleFile(pathIn, pathOut, keys);

	}

	public static int decryptFile(Path pathIn, Path pathOut, long[] keys, EncryptionType type, 
			JProgressBar pbBar) throws IOException {
		progBar = pbBar;
		encryptionType = type;
		status = Status.DECRYPTING;
		return handleFile(pathIn, pathOut, keys);
	}

	private static int handleFile(Path pathIn, Path pathOut, long[] keys) throws IOException {
		long startTime = System.currentTimeMillis();

		Encryption encryptor;
		
		if (encryptionType == EncryptionType.DES) {
			encryptor = new DES(keys[0]);
		} else {
			encryptor = new TripleDES(keys[0], keys[1]);
		}
	

		OutputStream outs = Files.newOutputStream(pathOut);
		InputStream ins = Files.newInputStream(pathIn);

		DataOutputStream out = new DataOutputStream(outs);
		DataInputStream in = new DataInputStream(ins);

		// wanneer we een file inlezen en de laatste 64 bits zijn niet volledig
		// ingevuld moeten we dat apart opslaan zodat identiek hetzelfde bestand
		// terug kan verkregen worden na decrypteren
		long totalBytesOriginal; // aantal bytes van het originele bestand

		if (status == Status.ENCRYPTING) {
			totalBytesOriginal = pathIn.toFile().length();
			// schrijf dit nummer weg op het begin van het .des bestand
			out.writeLong(totalBytesOriginal);

		} else {
			// wanneer we decrypten wordt het getal eerst ingelezen
			totalBytesOriginal = in.readLong();
		}

		// zoek hoeveel bytes de laatste long zal hebben
		int numberOfBytesLastLong = (int) (totalBytesOriginal % 8);

		// sla op welke waarde er 1% omhoog is voor de progressbar
		long progressDifference = totalBytesOriginal / 100;
		byte currentProgress = 0;

		// for loop voor het encryperen/decrypteren van alle 64-bits blokken
		// behalve de laatste
		for (long bytesPosition = 0; bytesPosition <= totalBytesOriginal - 8; bytesPosition += 8) {
			long l = in.readLong();

			if (status == Status.ENCRYPTING) {
				l = encryptor.encryptLong(l);
			} else {
				l = encryptor.decryptLong(l);
			}

			out.writeLong(l);

			if (progressDifference != 0) {
				byte testProgress = (byte) (bytesPosition / progressDifference);
				if (testProgress != currentProgress) {
					currentProgress = testProgress;
					progBar.setValue(currentProgress);
				}
			}

		}

		// System.out.println("total " + totalBytesOriginal + "postLast " +
		// bytesPositionLast + "number " + numberOfBytesLastLong);

		// behandelen laatste blok, aangezien deze verschillende lengtes kan
		// hebben
		if (numberOfBytesLastLong != 0) {
			if (status == Status.ENCRYPTING) {
				byte[] bytes = new byte[8];
				// inlezen van enkel de laatste x bytes
				in.read(bytes, 0, numberOfBytesLastLong);

				// deze omzetten naar een long
				long l = toLong(bytes);

				// long encrypteren
				l = encryptor.encryptLong(l);

				// long wegschrijven
				out.writeLong(l);
			} else {
				// laatste long inlezen
				long l = in.readLong();

				// long decrypteren
				l = encryptor.decryptLong(l);

				// omzetten naar een array van bytes
				byte[] bytes = toByta(l);

				// laatste x bytes toevoegen
				out.write(bytes, 0, numberOfBytesLastLong);
			}

		}

		progBar.setValue(100);

		in.close();
		out.close();

		long endTime = System.currentTimeMillis();

		return (int) (endTime - startTime);

	}

	// http://www.daniweb.com/software-development/java/code/216874
	public static byte[] toByta(long data) {
		return new byte[] { (byte) ((data >> 56) & 0xff),
				(byte) ((data >> 48) & 0xff), (byte) ((data >> 40) & 0xff),
				(byte) ((data >> 32) & 0xff), (byte) ((data >> 24) & 0xff),
				(byte) ((data >> 16) & 0xff), (byte) ((data >> 8) & 0xff),
				(byte) ((data >> 0) & 0xff), };
	}

	// http://www.daniweb.com/software-development/java/code/216874
	public static long toLong(byte[] data) {
		if (data == null || data.length != 8)
			return 0x0;
		// ----------
		return (long) (
		// (Below) convert to longs before shift because digits
		// are lost with ints beyond the 32-bit limit
		(long) (0xff & data[0]) << 56 | (long) (0xff & data[1]) << 48
				| (long) (0xff & data[2]) << 40 | (long) (0xff & data[3]) << 32
				| (long) (0xff & data[4]) << 24 | (long) (0xff & data[5]) << 16
				| (long) (0xff & data[6]) << 8 | (long) (0xff & data[7]) << 0);
	}

}
